/*
 * Copyright 1999-2019 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.idanchuang.component.sentinel.interceptor;

import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.idanchuang.component.sentinel.SentinelMsg;
import com.idanchuang.component.sentinel.config.SentinelConfigBean;
import com.idanchuang.component.sentinel.runner.SentinelConfigRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * Spring Web MVC interceptor that integrates with Sentinel.
 *
 * @author kaizi2009
 * @since 1.7.1
 */
public class ComponentSentinelWebInterceptor extends AbstractSentinelInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(ComponentSentinelWebInterceptor.class);

    private final SentinelWebMvcConfig config;

    public ComponentSentinelWebInterceptor() {
        this(new SentinelWebMvcConfig());
    }

    public ComponentSentinelWebInterceptor(SentinelWebMvcConfig config) {
        super(config);
        this.config = config;
    }

    @Override
    protected String getResourceName(HttpServletRequest request) {
        // Resolve the Spring Web URL pattern from the request attribute.
        Object resourceNameObject = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        if (!(resourceNameObject instanceof String)) {
            return null;
        }
        String resourceName = (String) resourceNameObject;
        UrlCleaner urlCleaner = config.getUrlCleaner();
        if (urlCleaner != null) {
            resourceName = urlCleaner.clean(resourceName);
        }
        // Add method specification if necessary
        if (StringUtil.isNotEmpty(resourceName) && config.isHttpMethodSpecify()) {
            resourceName = request.getMethod().toUpperCase() + ":" + resourceName;
        }
        return resourceName;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        try {
            // if 配置还未初始化, 则循环等待
            while (Constants.ON && !SentinelConfigRunner.configLoaded) {
                Thread.sleep(1000L);
            }
            String resourceName = getResourceName(request);

            if (StringUtil.isNotEmpty(resourceName)) {
                // Parse the request origin using registered origin parser.
                String origin = parseOrigin(request);
                ContextUtil.enter(SENTINEL_SPRING_WEB_CONTEXT_NAME, origin);

                Object[] args = new Object[0];
                if (handler instanceof HandlerMethod) {
                    Parameter[] parameters = ((HandlerMethod)handler).getMethod().getParameters();
                    if (parameters.length > 0) {
                        args = new Object[parameters.length];
                        for (int i = 0; i < args.length; i ++) {
                            String name = parameters[i].getName();
                            args[i] = name != null ? request.getParameter(name) : "";
                        }
                    }
                }

                Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN, args);

                setEntryInRequest(request, config.getRequestAttributeName(), entry);
            }
            return true;
        } catch (BlockException e) {
            try {
                if (handler instanceof HandlerMethod) {
                    Method method = ((HandlerMethod) handler).getMethod();
                    if (method.isAnnotationPresent(SentinelMsg.class)) {
                        String msg = method.getAnnotation(SentinelMsg.class).value();
                        request.setAttribute(SentinelConfigBean.SENTINEL_MSG_KEY, msg);
                    }
                }
            } catch (Exception e1) {
                logger.error("ComponentSentinelWebInterceptor custom msg error", e1);
            }
            try {
                handleBlockException(request, response, e);
            } finally {
                ContextUtil.exit();
            }
            return false;
        } catch (Exception e) {
            logger.warn("ComponentSentinelWebInterceptor > preHandle error", e);
            return true;
        }
    }

}
